# -*- coding: utf-8 -*-

import socket
import time
import threading
import logging
import struct
import queue

import message_types_res as message_types
import time
import datetime
# import datetime as dt

import utility
from pybus import bus_singleton
import app_common as app_com

HEADER_LENGTH = 16

class TCPClient:
    def __init__(self, cfg, types):
        logging.basicConfig(format='%(asctime)s - %(pathname)s[line:%(lineno)d] - %(levelname)s: %(message)s',\
            level=logging.DEBUG)
        self.cfg = cfg
        self.msg_types = types
        self.write_queue = queue.Queue() # 写数据队列
        self.cmd_queue = queue.Queue()  # 专门存放命令信息队列，如果需要存放大数据量建议单独创建队列
        self.stopped = True         # tcp session是否停止
        self.recv_thread = None     # 接收数据线程
        self.write_thread = None    # 写数据线程
        self.is_reconnect = bool(self.cfg["is_reconnect"])  # 如果开启重连，创建重连定时器
        if self.is_reconnect and self.cfg["reconnect_interval"]:
            self.reconnect_timer = utility.RepeatedTimer(int(self.cfg["heartbeat_interval"]), self.reconnect)
        
    def stop(self):
        if self.stopped:
            return 
        
        self.stopped = True
        bus_singleton.publish(app_com.kMsgTcpClose) # 通知tcp链路断开

        with self.write_queue.mutex:
            self.write_queue.queue.clear()
        with self.cmd_queue.mutex:
            self.cmd_queue.queue.clear()
            
        if self._socket:
            self._socket.close()
            self._socket = None

    def is_stopped(self):
        return self.stopped

    def reconnect(self):
        self.stop()
        logging.info("reconnect...")
        self.start()


    def read_msg(self):
        while True:
            if self.stopped:
                break
                
            data_header = b''
            # read header, why little-endian?
            while len(data_header) < HEADER_LENGTH:
                try:
                    data = self._socket.recv(HEADER_LENGTH - len(data_header))
                except socket.error as e:
                    logging.error("socket recv header error: %s", e)
                    break
                if not data or len(data) == 0:
                    logging.error("recv header error, expect %s bytes, recv %s bytes", HEADER_LENGTH, len(data_header))
                    break
                data_header += data

            try:
                sync_bytes, full_len, message_type, body_len = struct.unpack('<IIII', data_header)
            except struct.error as e:
                logging.error("unpack header failed: %s", e)
                self.stop()
                if self.is_reconnect and not self.reconnect_timer.is_running:
                    self.reconnect_timer.start()
                break

            # read body
            body = b''
            body_length = full_len - HEADER_LENGTH
            # print("body_length===",body_length)
            while len(body) < body_length:
                try:
                    data = self._socket.recv(body_length - len(body))
                except socket.error as e:
                    logging.error("socket recv body error: %s", e)
                    break

                if not data or len(data) == 0:
                    logging.error("recv body error, expect %s bytes, recv %s bytes", body_length, len(body))
                    break
                body += data
            
            try:
                message_json, message_binary = struct.unpack('<%ds%ds' % (body_len, body_length-body_len), body)
            except struct.error as e:
                logging.error("unpack body failed: %s", e)
                break

            recv_msg = {}
            recv_msg["sync_bytes"] = sync_bytes
            recv_msg["full_len"] = full_len
            recv_msg["type"] = message_type
            recv_msg["body_len"] = body_len
            recv_msg["body"] = message_json
            recv_msg["binary"] = message_binary
            
            # for test
            if sync_bytes == self.msg_types[message_types.kSyncBytes]:
                find_flag = 0
                for _, v in self.msg_types.items():
                    if message_type == v:
                        self.cmd_queue.put(recv_msg)
                        find_flag = 1
                        break

                if find_flag == 0:
                    logging.error("the message types %s is invalid", message_type)
            else:
                logging.error("the message sync bytes is invalid: %s", sync_bytes)


    def write_msg(self):
        logging.debug("start write msg")
        while True:
            if self.stopped:
                break
            
            msg = self.write_queue.get()
            # logging.debug("write queue size: %s", self.write_queue.qsize())
            sync_bytes = self.msg_types[message_types.kSyncBytes]
            body = msg["body"]
            body_len = len(body)
            typ = msg["type"]
            binary_str = b""
            full_len= HEADER_LENGTH + body_len + len(binary_str)

            try:
                message = struct.pack('<IIII%ds%ds' % (body_len, len(binary_str)),\
                    sync_bytes, full_len, typ, body_len,\
                    body, binary_str)
                # logging.debug("write_msg: %s", message)
            except struct.error as e:
                logging.error("pack message failed: %s", e)

            try:
                self._socket.sendall(message)
            except socket.error as e:
                logging.error("socket send error: %s", e)
                self.stop()
                if self.is_reconnect and not self.reconnect_timer.is_running:
                    self.reconnect_timer.start()
                break

    def send_to_queue(self, msg):
        self.write_queue.put(msg)

    def start(self):
        try:
            self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        except socket.error as e:
            logging.error("create socket error: %s", e)
            self.stop()
            return
        
        try:
            self._socket.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1)
            self._socket.connect((self.cfg["server_ip"], int(self.cfg["server_port"])))
        except socket.error as e:
            logging.error("socket error: %s", e)
            if self.is_reconnect and not self.reconnect_timer.is_running:
                self.reconnect_timer.start()
            return
        self.doConnected()
       
        
    def doConnected(self):
        if self.reconnect_timer.is_running:
            self.reconnect_timer.stop()
        self.stopped = False
        self.recv_thread = threading.Thread(target=self.read_msg)
        self.recv_thread.setDaemon(True)    # 解决主进程调用exit无法退出
        self.recv_thread.start()
        self.write_thread = threading.Thread(target=self.write_msg)
        self.write_thread.setDaemon(True) 
        self.write_thread.start()
        bus_singleton.publish(app_com.kMsgTcpConnected) # 通知tcp连接已创建
        






